using System;
using System.Collections.Generic;
using System.Text;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;
using System.IO;

namespace MvcEngine.Core.Helpers
{
    public class ZipHelper :IZipHelper
    {
        #region Private fields

        private static readonly int DefaultCompressionLevel = 6; 

        #endregion

        #region Public static properties

        public static IZipHelper Current
        {
            get { return IoC.Resolve<IZipHelper>(); }
        } 

        #endregion

        #region Public methods

        public void PackDirectory(string pathToDirectory, string outputFileName)
        {
            PackDirectory(pathToDirectory, outputFileName, DefaultCompressionLevel);
        }

        public void PackDirectory(string pathToDirectory, string outputFileName, int compressionLevel)
        {
            using (ZipOutputStream zipOutStream = new ZipOutputStream(File.Create(outputFileName)))
            {
                PackDirectory(pathToDirectory, pathToDirectory, zipOutStream, compressionLevel);
            }
        }

        public void UnpackArchive(string fileName, string directoryToExtract)
        {
            using (Stream stream = File.Open(fileName, FileMode.Open))
            {
                UnpackArchive(stream, directoryToExtract);
            }

        }

        public void UnpackArchive(Stream fileStream, string directoryToExtract)
        {
            using (ZipInputStream zipInputStream = new ZipInputStream(fileStream))
            {
                ZipEntry entry;
                while ((entry = zipInputStream.GetNextEntry()) != null)
                {
                    UnpackEntry(zipInputStream, entry, directoryToExtract);
                }
            }
        } 

        #endregion

        #region Private methods

        private void PackDirectory(string pathToDirectory, string rootPath, ZipOutputStream zipOutStream, int compressionLevel)
        {
            rootPath = NormalizeRootPath(rootPath);
            zipOutStream.SetLevel(compressionLevel); // 0 - store only to 9 - means best compression

            string[] files = Directory.GetFiles(pathToDirectory);
            foreach (string file in files)
            {
                PackFile(pathToDirectory, rootPath, file, zipOutStream);
            }
            string[] directories = Directory.GetDirectories(pathToDirectory);
            foreach (string directory in directories)
            {
                string path = Path.Combine(pathToDirectory, directory);
                PackDirectory(path, rootPath, zipOutStream, compressionLevel);
            }
        }

        private string NormalizeRootPath(string rootPath)
        {
            if (rootPath.EndsWith("\\"))
                return rootPath;
            else
                return rootPath + "\\";
        }

        private void PackFile(string pathToDirectory, string rootPath, string fileName, ZipOutputStream zipOutStream)
        {
            string relativePath = String.Empty;
            if (pathToDirectory.Length > rootPath.Length)
            {
                relativePath = pathToDirectory.Substring(rootPath.Length);
            }

            byte[] memoryBuffer = File.ReadAllBytes(Path.Combine(pathToDirectory, fileName));

            string name = new FileInfo(fileName).Name;
            name = Path.Combine(relativePath, name);
            ZipEntry memoryEntry = new ZipEntry(name);
            memoryEntry.DateTime = DateTime.Now;
            memoryEntry.Size = memoryBuffer.Length;

            Crc32 crc = new Crc32();
            crc.Reset();
            crc.Update(memoryBuffer);

            memoryEntry.Crc = crc.Value;

            zipOutStream.PutNextEntry(memoryEntry);

            zipOutStream.Write(memoryBuffer, 0, memoryBuffer.Length);
        }

        private void UnpackEntry(ZipInputStream zipInputStream, ZipEntry entry, string directoryToExtract)
        {
            if (entry.IsFile)
            {
                string fileName = Path.Combine(directoryToExtract, entry.Name);
                string dir = Path.GetDirectoryName(fileName);

                if (!Directory.Exists(dir))
                    Directory.CreateDirectory(dir);

                using (FileStream fileStream = File.Create(fileName))
                {
                    int size;
                    byte[] data = null;

                    if (entry.Size > 100000)
                        data = new byte[100000];
                    else
                        data = new byte[entry.Size];

                    while (true)
                    {
                        size = zipInputStream.Read(data, 0, data.Length);
                        if (size > 0)
                        {
                            fileStream.Write(data, 0, size);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        } 

        #endregion
    }
}
